今天要來介紹 prisma
的 extension
,前幾天我們透過 Prisma.validator
讓我們的 prisma
的 queries
有 type safe
的功能,但其實還有別種 customer
的方式,今天我們將介紹如何使用 zod
去幫我們確保 prisma
的 query input
,那我們廢話不多說走起~
以下是今天的 model
user
可以有多個 post
email
model
去紀錄有使用過的 email
有哪些model User {
id Int @id @default(autoincrement())
email String
firstName String
lastName String
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
published Boolean @default(true)
content String?
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}
model Email {
id Int @id @default(autoincrement())
value String @unique
created_at DateTime @default(now())
updated_at DateTime @updatedAt
}
之後 migrate
好 DB
後我們先到 prisma studio
塞一些 email data
,這邊得 email list
的用處會是等下用來驗證 user create
的 email
接著我們寫一個 getEmails
function
去拿到所有合法的 email list
const getEmails = async () => {
const emails = await prismaClient.email.findMany({})
return emails
}
結果如下,可以看到這邊清楚記錄合法 email
存在的時間以及修改的時間
[
{
id: 1,
value: 'hiunji64@gmail.com',
created_at: 2024-10-02T14:02:21.987Z,
updated_at: 2024-10-02T14:02:13.491Z
}
]
之後我們 install zod
>npm i zod
接著我們定好 user create
的 zod schema
:
firstName
為必填lastName
為必填email
必須是 email
的格式,同時 value
只能用 getEmails
return
的 email list
const userCreateInputSchema = z.object({
firstName: z.string(),
lastName: z.string(),
email: z
.string()
.email('this is not validate emails')
.refine(async (email) => {
const emails = await getEmails()
return emails.findIndex(({ value }) => value === email) !== -1
}, 'This email is not in our database')
})
現在我們做好了一個合法的 email list
,也定好了 input schema
,之後我們需要跟 prisma client
溝通,以確保我們的 query
可以透過我們定好的 schema
去驗證,在 prisma client
有一個 Extension
可以做到,至於怎麼做到的我們接著看以下的 Demo
~
在 $extends
中有一個 query
fields
裡面有各種我們的 model key
,這邊因為我們要驗證 user create
,所以我們就驗證 user
底下的 methods
const prismaClient = new PrismaClient().$extends({
query: {
user: {
create: async ({ args, query }) => {
args.data = await userCreateInputSchema.parseAsync(args.data)
return query(args)
},
update: async ({ args, query }) => {
args.data = await userCreateInputSchema.parseAsync(args.data)
return query(args)
},
updateMany: async ({ args, query }) => {
args.data = await userCreateInputSchema.partial().parseAsync(args.data)
return query(args)
},
upsert: async ({ args, query }) => {
args.create = await userCreateInputSchema.parseAsync(args.create)
args.update = await userCreateInputSchema.partial().parseAsync(args.update)
return query(args)
},
}
}
})
每個 methods
都有兩個屬性,args
跟 query
, args
會是在使用 prisma client
時你 input
的 value
例如我們在 create User
時候我們會用 create api
在 data
輸入的 object
像是 firstName
等等他等同於 args.data
,所以我們才會用userCreateInputSchema.parseAsync
去 parse
args.data
const user = await prismaClient.user.create({
data: {
firstName: 'Danny',
lastName: 'Wu',
email: "hiunji64@gmail.com"
}
})
那 query(args)
就是讓 prisma
去執行我們輸入的 input
create: async ({ args, query }) => {
args.data = await userCreateInputSchema.parseAsync(args.data)
return query(args)
}
然後我們寫一個 main function
去測試我們 prisma
的 $extends
結果
const main = async () => {
try {
const user = await prismaClient.user.create({
data: {
firstName: 'Danny',
lastName: 'Wu',
email: "sdfsdf@gmail.com"
}
})
} catch (error) {
if (error instanceof ZodError) {
console.log(error.formErrors)
}
}
}
你會看到當我輸入一個不存在 DB
的 email
時候,會自動跳出 This email is not in our database
的 message
去提醒你的 input
是無效的
{
formErrors: [],
fieldErrors: { email: [ 'This email is not in our database' ] }
}
甚至如果 email
格式不對,也會跳 zod error
提醒你格式要符合 email
const user = await prismaClient.user.create({
data: {
firstName: 'Danny',
lastName: 'Wu',
email: "sdfsdf"
}
})
{
formErrors: [],
fieldErrors: {
email: [
'this is not validate emails',
'This email is not in our database'
]
}
}
所以如果輸入合法的 email
的話就可以成功 create user
const user = await prismaClient.user.create({
data: {
firstName: 'Danny',
lastName: 'Wu',
email: "hiunji64@gmail.com"
}
})
看到以下的 result
就代表你成功了~
{
id: 4,
email: 'hiunji64@gmail.com',
firstName: 'Danny',
lastName: 'Wu',
fullName: 'Danny Wu'
}
最後加碼一個內容就是,有的時候我們可能需要根據特定的欄位去結合我們要的資料,但又不想動到 DB
的欄位的時候,我們可以使用 Computed
的功能,舉個例子來說,以我們的 User
Model
為例子,我們有 firstName
跟 lastName
,我們很常會需要一個 fullName
的資料呈現,這時用 Computed
就很適合,使用的方式很簡單我們一樣要在 $extends
加上一個 result
欄位:
const prismaClient = new PrismaClient().$extends({
query: {
user: {
create: async ({ args, query }) => {
args.data = await userCreateInputSchema.parseAsync(args.data)
return query(args)
},
update: async ({ args, query }) => {
args.data = await userCreateInputSchema.parseAsync(args.data)
return query(args)
},
updateMany: async ({ args, query }) => {
args.data = await userCreateInputSchema.partial().parseAsync(args.data)
return query(args)
},
upsert: async ({ args, query }) => {
args.create = await userCreateInputSchema.parseAsync(args.create)
args.update = await userCreateInputSchema.partial().parseAsync(args.update)
return query(args)
},
}
},
result: {
user: {
fullName: {
needs: { firstName: true, lastName: true },
compute: (user) => {
return `${user.firstName} ${user.lastName}`
}
}
}
}
})
result
: 用來管理 model
return
的內容needs
: 表示哪些欄位必須要有 value
你才能夠 compute
compute
: compute return
的結果之後我們 query
一下 data
const user = await prismaClient.user.findFirst({
where: {
email: 'hiunji64@gmail.com'
}
})
你會看到我們成功多一個 fullName
欄位了,是不是很開心又激動呢~
{
id: 1,
email: 'hiunji64@gmail.com',
firstName: 'Danny',
lastName: 'Wu',
fullName: 'Danny Wu'
}
✅ 前端社群 :
https://lihi3.cc/kBe0Y